#ifndef __UTILITY_H__
#define __UTILITY_H__

#include "vec2.h"
#include "vec3.h"
#include "vec4.h"
#include "mat2.h"
#include "mat3.h"
#include "mat4.h"
#include "dvec2.h"
#include "dvec3.h"
#include "dvec4.h"
#include "dmat2.h"
#include "dmat3.h"
#include "dmat4.h"

namespace libvec
{
  //===========================================================================
  // OpenGL Shader Language
  //===========================================================================

  // converts degrees to and from radians
  float radians(float degrees);
  double radians(double degrees);
  float degrees(float radians);
  double degrees(double radians);

  // returns the length of vector
  float length(vec2 v);
  float length(vec3 v);
  float length(vec4 v);
  double length(dvec2 v);
  double length(dvec3 v);
  double length(dvec4 v);

  // returns the distance between p0 and p1
  float distance(vec2 p0, vec2 p1);
  float distance(vec3 p0, vec3 p1);
  float distance(vec4 p0, vec4 p1);
  double distance(dvec2 p0, dvec2 p1);
  double distance(dvec3 p0, dvec3 p1);
  double distance(dvec4 p0, dvec4 p1);

  // returns the dot product of x and y
  float dot(vec2 x, vec2 y);
  float dot(vec3 x, vec3 y);
  float dot(vec4 x, vec4 y);
  double dot(dvec2 x, dvec2 y);
  double dot(dvec3 x, dvec3 y);
  double dot(dvec4 x, dvec4 y);

  // returns the cross product of x and y
  vec3 cross(vec3 v1, vec3 v2);
  dvec3 cross(dvec3 v1, dvec3 v2);

  // returns a vector in the direction of v and length of 1
  vec2 normalize(vec2 v);
  vec3 normalize(vec3 v);
  vec4 normalize(vec4 v);
  dvec2 normalize(dvec2 v);
  dvec3 normalize(dvec3 v);
  dvec4 normalize(dvec4 v);

  // check input is on forward face of n
  vec2 faceforward(vec2 n, vec2 i, vec2 nref);
  vec3 faceforward(vec3 n, vec3 i, vec3 nref);
  vec4 faceforward(vec4 n, vec4 i, vec4 nref);
  dvec2 faceforward(dvec2 n, dvec2 i, dvec2 nref);
  dvec3 faceforward(dvec3 n, dvec3 i, dvec3 nref);
  dvec4 faceforward(dvec4 n, dvec4 i, dvec4 nref);

  // return the reflection of v on normal n
  vec2 reflect(vec2 i, vec2 n);
  vec3 reflect(vec3 i, vec3 n);
  vec4 reflect(vec4 i, vec4 n);
  dvec2 reflect(dvec2 i, dvec2 n);
  dvec3 reflect(dvec3 i, dvec3 n);
  dvec4 reflect(dvec4 i, dvec4 n);

  // returns the refract of v on normal n and ratio eta
  vec2 refract(vec2 i, vec2 n, float eta);
  vec3 refract(vec3 i, vec3 n, float eta);
  vec4 refract(vec4 i, vec4 n, float eta);
  dvec2 refract(dvec2 i, dvec2 n, double eta);
  dvec3 refract(dvec3 i, dvec3 n, double eta);
  dvec4 refract(dvec4 i, dvec4 n, double eta);

  // multiply matrix x by y component-wise
  mat2 matrixCompMult(mat2 x, mat2 y);
  mat3 matrixCompMult(mat3 x, mat3 y);
  mat4 matrixCompMult(mat4 x, mat4 y);
  dmat2 matrixCompMult(dmat2 x, dmat2 y);
  dmat3 matrixCompMult(dmat3 x, dmat3 y);
  dmat4 matrixCompMult(dmat4 x, dmat4 y);

  // returns the outer product of two vectors as col and row
  mat2 outerProduct(vec2 c, vec2 r);
  mat3 outerProduct(vec3 c, vec3 r);
  mat4 outerProduct(vec4 c, vec4 r);
  dmat2 outerProduct(dvec2 c, dvec2 r);
  dmat3 outerProduct(dvec3 c, dvec3 r);
  dmat4 outerProduct(dvec4 c, dvec4 r);

  // returns a matrix that is the transpose of m
  mat2 transpose(mat2 m);
  mat3 transpose(mat3 m);
  mat4 transpose(mat4 m);
  dmat2 transpose(dmat2 m);
  dmat3 transpose(dmat3 m);
  dmat4 transpose(dmat4 m);

  // returns the determinant of m
  float determinant(mat2 m);
  float determinant(mat3 m);
  float determinant(mat4 m);
  double determinant(dmat2 m);
  double determinant(dmat3 m);
  double determinant(dmat4 m);

  // returns a matrix that is the inverse of m
  mat2 inverse(mat2 m);
  mat3 inverse(mat3 m);
  mat4 inverse(mat4 m);
  dmat2 inverse(dmat2 m);
  dmat3 inverse(dmat3 m);
  dmat4 inverse(dmat4 m);

  //===========================================================================
  // Helper Functions
  //===========================================================================

  // apply translatation on matrix m
  mat3 translate(mat3 m, vec2 v);
  mat3 translate(mat3 m, float x, float y);
  mat4 translate(mat4 m, vec3 v);
  mat4 translate(mat4 m, float x, float y, float z);
  dmat3 translate(dmat3 m, dvec3 v);
  dmat3 translate(dmat3 m, double x, double y);
  dmat4 translate(dmat4 m, dvec3 v);
  dmat4 translate(dmat4 m, double x, double y, double z);

  // apply scale on matrix m with axis (DO NOT USE IN 3D)
  mat3 scaleX(mat3 m, float k);
  mat4 scaleX(mat4 m, float k);
  mat3 scaleY(mat3 m, float k);
  mat4 scaleY(mat4 m, float k);
  mat4 scaleZ(mat4 m, float k);
  dmat3 scaleX(dmat3 m, double k);
  dmat4 scaleX(dmat4 m, double k);
  dmat3 scaleY(dmat3 m, double k);
  dmat4 scaleY(dmat4 m, double k);
  dmat4 scaleZ(dmat4 m, double k);

  // apply scale on matrix m on all axis
  mat3 scale(mat3 m, float k);
  mat4 scale(mat4 m, float k);
  dmat3 scale(dmat3 m, double k);
  dmat4 scale(dmat4 m, double k);

  // apply rotation on matrix m with axis
  mat4 rotateX(mat4 m, float angle);
  mat4 rotateY(mat4 m, float angle);
  mat4 rotateZ(mat4 m, float angle);
  dmat4 rotateX(dmat4 m, double angle);
  dmat4 rotateY(dmat4 m, double angle);
  dmat4 rotateZ(dmat4 m, double angle);

  // apply rotation on matrix m with any axis
  mat3 rotate(mat3 m, float angle);
  mat4 rotate(mat4 m, float angle, vec3 v);
  mat4 rotate(mat4 m, float angle, float x, float y, float z);
  dmat3 rotate(dmat3 m, double angle);
  dmat4 rotate(dmat4 m, double angle, dvec3 v);
  dmat4 rotate(dmat4 m, double angle, double x, double y, double z);

  // generate projection matrices
  mat4 orthof(float left, float right, float bottom, float top, float near,
      float far);
  dmat4 orthod(double left, double right, double bottom, double top,
      double near, double far);
  mat4 frustumf(float left, float right, float bottom, float top, float near,
      float far);
  dmat4 frustumd(double left, double right, double bottom, double top,
      double near, double far);
  mat4 perspectivef(float fov, float aspect, float near, float far);
  dmat4 perspectived(double fov, double aspect, double near, double far);

  // generate view matrices
  mat4 lookatf(vec3 eye, vec3 center, vec3 up);
  dmat4 lookatd(dvec3 eye, dvec3 center, dvec3 up);
}

#endif /* __UTILITY_H__ */
